package golf.stockinfo.buysellsimulation;

import golf.stockinfo.buysellsimulation.SimulationData.TransactionType;

import java.io.Serializable;
import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;

import org.joda.time.DateTime;

public class SimulateSET_SET50_CombinedApproach implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = -762611353588390331L;
	
	public ArrayList<SimulationData> execute(boolean printDetailedSimulation,
			double initialCapital,
			short smavETF,
			short smavTFEX,
			Date startDate,
			int simulationLengthInYear,
			boolean playLong,
			boolean playShort,
			boolean playETFonLong, 
			double maxLossOnBalanceForTFEX, 
			double maxLossOnBalanceForETF, 
			double lossStopThresholdPerContract,
			int cappedContractNumber, 
			double profitTakingThresholdETF, 
			double profitTakingThresholdTFEX, 
			boolean profitTaking, 
			boolean isContinousInvesting, 
			double allowedMinimumRunningBalance){
		
		ArrayList<SimulationData> output = new ArrayList<SimulationData>();
		
		boolean isInvested = false;

		double runningBalance = initialCapital;
		
		try {
			TreeMap<Long, MarketData> weeklyDataETF = SimulationCommonUtils.loadMarketDataFromDB(getConnection(), smavETF, "EFIN", "SET", "WEEKLY");
			TreeMap<Long, MarketData> weeklyDataTFEX = SimulationCommonUtils.loadMarketDataFromDB(getConnection(), smavTFEX, "EFIN", "SET", "WEEKLY");
			
			DateTime stopDate = null;
			DateTime currDate = null;
			SimulationData simulationItem = null;
			boolean maxGainIsDropped = false;
			
			for(Map.Entry<Long, MarketData> thisWeekCursor : weeklyDataETF.entrySet()){
				
				MarketData thisWeek = thisWeekCursor.getValue();
				
				if(stopDate == null){
					stopDate = new DateTime(startDate).plusYears(simulationLengthInYear).plusMonths(3);
				}
				
				currDate = new DateTime(thisWeek.getDate());
				
				if(currDate.isAfter(startDate.getTime()) 
						&& currDate.isBefore(stopDate.getMillis())
						&& weeklyDataTFEX.get(thisWeekCursor.getKey()) != null){
					
					MarketData thisWeekTFEX = weeklyDataTFEX.get(thisWeekCursor.getKey());
					
					if( ! isInvested ){
						
						if( playLong 
								&& ((playETFonLong && thisWeek.getLowIndex() > thisWeek.getCloseIndexSMAV())
										|| ( (!playETFonLong) && thisWeekTFEX.getLowIndex() > thisWeekTFEX.getCloseIndexSMAV()))){
							//This means the candle stick is above the SMAV line
							simulationItem = new SimulationData();
							if(playETFonLong){
								simulationItem.setTypeOfTransaction(TransactionType.BUY_SET50);
							}else{
								simulationItem.setTypeOfTransaction(TransactionType.LONG_TFEX);
							}
							
							isInvested = true;
							maxGainIsDropped = false;

						}else if( playShort 
								&& (thisWeekTFEX.getHighIndex() < thisWeekTFEX.getCloseIndexSMAV())){
							//This means the candle stick is under the SMAV line
							simulationItem = new SimulationData();
							simulationItem.setTypeOfTransaction(TransactionType.SHORT_TEFX);
							
							isInvested = true;
							maxGainIsDropped = false;

						}
						
						if(isInvested){
							
							simulationItem.setStartDate(thisWeek.getDate());
							simulationItem.setStartIndex(
									SimulationCommonUtils.getClosingIndexOfWeek(
											getConnection(), thisWeek, "SET50", "EFIN", "DAILY"));
							
							if(! simulationItem.getTypeOfTransaction().equals(TransactionType.BUY_SET50)){
								double numberOfContractByLossPerContract = Math.abs(runningBalance * maxLossOnBalanceForTFEX / lossStopThresholdPerContract);
								double numberOfContractByMargin = Math.abs(runningBalance / SimulationConstant.TFEX_SET50_MARGIN_PER_CONTRACT);
								
								if(numberOfContractByMargin < numberOfContractByLossPerContract){
									simulationItem.setNumberOfTFEXContract((int)SimulationCommonUtils.roundOff(numberOfContractByMargin, 0));
								}else{
									simulationItem.setNumberOfTFEXContract((int)SimulationCommonUtils.roundOff(numberOfContractByLossPerContract, 0));
								}
								
								if(simulationItem.getNumberOfTFEXContract() <= 0){
									simulationItem.setNumberOfTFEXContract(1);
								}else if(simulationItem.getNumberOfTFEXContract() > cappedContractNumber){
									simulationItem.setNumberOfTFEXContract(cappedContractNumber);
								}
							}
							
						}
						
					}else{
						
						ArrayList<MarketData> marketDataDaily = SimulationCommonUtils.loadMarketDataDaily(
								getConnection(), thisWeek.getDate(), thisWeek.getNextDate(), "EFIN", "SET50");
						
						if(simulationItem.getStartDate().equals(Date.valueOf("2007-04-09"))){
							System.out.print("");
						}
						
						for(MarketData marketData : marketDataDaily){
							
							double maxLoss = 0, maxGain = 0;
							
							if(simulationItem.getTypeOfTransaction().equals(TransactionType.LONG_TFEX)){
								maxLoss = (marketData.getLowIndex() - simulationItem.getStartIndex()) * SimulationConstant.TFEX_PRICE_PER_POINT;
							}else if(simulationItem.getTypeOfTransaction().equals(TransactionType.SHORT_TEFX)){
								maxLoss = (simulationItem.getStartIndex() - marketData.getHighIndex()) * SimulationConstant.TFEX_PRICE_PER_POINT;
							}else if(simulationItem.getTypeOfTransaction().equals(TransactionType.BUY_SET50)){
								maxLoss = (marketData.getLowIndex() - simulationItem.getStartIndex())/simulationItem.getStartIndex() * runningBalance;
							}

							if(simulationItem.getTypeOfTransaction().equals(TransactionType.LONG_TFEX)){
								maxGain = (marketData.getHighIndex() - simulationItem.getStartIndex()) * SimulationConstant.TFEX_PRICE_PER_POINT;
							}else if(simulationItem.getTypeOfTransaction().equals(TransactionType.SHORT_TEFX)){
								maxGain = (simulationItem.getStartIndex() - marketData.getLowIndex()) * SimulationConstant.TFEX_PRICE_PER_POINT;
							}else if(simulationItem.getTypeOfTransaction().equals(TransactionType.BUY_SET50)){
								maxGain = (marketData.getHighIndex() - simulationItem.getStartIndex())/simulationItem.getStartIndex() * runningBalance;
							}
							
							if( ! simulationItem.getTypeOfTransaction().equals(TransactionType.BUY_SET50)){
								maxGain = maxGain * simulationItem.getNumberOfTFEXContract();
							}
							
							if(maxGain > simulationItem.getMaximumPointGainForPeriod_asDouble()){
								simulationItem.setMaximumPointGainForPeriod(maxGain);
							}else{
								if(!maxGainIsDropped
										&& simulationItem.getMaximumPointGainForPeriod_asDouble() > 0
										&& (simulationItem.getMaximumPointGainForPeriod_asDouble()/runningBalance >= profitTakingThresholdETF && simulationItem.getTypeOfTransaction().equals(TransactionType.BUY_SET50)
											|| simulationItem.getMaximumPointGainForPeriod_asDouble()/runningBalance >= profitTakingThresholdTFEX && !simulationItem.getTypeOfTransaction().equals(TransactionType.BUY_SET50))
										&& (( maxGain > 0 && (simulationItem.getMaximumPointGainForPeriod_asDouble() - maxGain)/maxGain > 0.1)
												|| maxGain < 0)){
									//Dropped by 10%
									maxGainIsDropped = true;
								}
							}
							
							if(((!simulationItem.getTypeOfTransaction().equals(TransactionType.BUY_SET50))
									&& maxLoss * simulationItem.getNumberOfTFEXContract() < simulationItem.getMaximumPointLossForPeriod_asDouble())
									|| (simulationItem.getTypeOfTransaction().equals(TransactionType.BUY_SET50)
											&& maxLoss < simulationItem.getMaximumPointLossForPeriod_asDouble())){
								if(simulationItem.getTypeOfTransaction().equals(TransactionType.BUY_SET50)){
									simulationItem.setMaximumPointLossForPeriod(maxLoss);
								}else{
									simulationItem.setMaximumPointLossForPeriod(maxLoss * simulationItem.getNumberOfTFEXContract());
								}
							}
							
							if(isInvested && SimulationCommonUtils.hasToStopLoss(
									simulationItem, maxLoss, runningBalance, lossStopThresholdPerContract, maxLossOnBalanceForETF)){
								
								//Stop the loss
								double lossAmount = 0;
								if(simulationItem.getTypeOfTransaction().equals(TransactionType.BUY_SET50)){
									lossAmount = maxLossOnBalanceForETF * runningBalance;
								}else{
									lossAmount = lossStopThresholdPerContract * simulationItem.getNumberOfTFEXContract();
								}
								simulationItem.setCutLossDate(thisWeek.getDate());
								simulationItem.setCutLossAmount(lossAmount);
								runningBalance = runningBalance + lossAmount;
								simulationItem.setBalance(runningBalance);
								
								if(isContinousInvesting){
									simulationItem.setEndDate(marketData.getDate());
									simulationItem.setEndIndex(marketData.getCloseIndex());
									output.add(simulationItem);
									isInvested = false;
								}
								
							}else if(isInvested && profitTaking && maxGainIsDropped
									&& (simulationItem.getMaximumPointGainForPeriod_asDouble()/runningBalance >= profitTakingThresholdETF && simulationItem.getTypeOfTransaction().equals(TransactionType.BUY_SET50)
											|| simulationItem.getMaximumPointGainForPeriod_asDouble()/runningBalance >= profitTakingThresholdTFEX && !simulationItem.getTypeOfTransaction().equals(TransactionType.BUY_SET50))
									&& simulationItem.getCutLossAmount() == 0){
								
								simulationItem.setTakeProfit(true);
								simulationItem.setEndDate(marketData.getDate());
								
								double gainLoss = 0;
								double closingIndexForToday = SimulationCommonUtils.getClosingIndexOfWeek(
										getConnection(), thisWeek, "SET50", "EFIN", "DAILY");
								if(simulationItem.getTypeOfTransaction().equals(TransactionType.LONG_TFEX)){
									gainLoss = (closingIndexForToday - simulationItem.getStartIndex()) 
											* SimulationConstant.TFEX_PRICE_PER_POINT 
											* simulationItem.getNumberOfTFEXContract();
								}else if(simulationItem.getTypeOfTransaction().equals(TransactionType.SHORT_TEFX)){
									gainLoss = (simulationItem.getStartIndex() - closingIndexForToday) 
											* SimulationConstant.TFEX_PRICE_PER_POINT 
											* simulationItem.getNumberOfTFEXContract();
								}else if(simulationItem.getTypeOfTransaction().equals(TransactionType.BUY_SET50)){
									gainLoss = (closingIndexForToday - simulationItem.getStartIndex())
											/ simulationItem.getStartIndex() 
											* runningBalance;
								}
								
								runningBalance = runningBalance + gainLoss; //assume that we sell position only after the max profit is dropped from the top by 20%, and use closing price for the day
								simulationItem.setBalance(runningBalance);
								
								output.add(simulationItem);
								isInvested = false;
								
							}
						}
						
						if(isInvested && SimulationCommonUtils.hasCutSMAV(simulationItem, thisWeek, thisWeekTFEX)){
							
							simulationItem.setEndDate(thisWeek.getDate());
							simulationItem.setEndIndex(SimulationCommonUtils.getClosingIndexOfWeek(
									getConnection(), thisWeek, "SET50", "EFIN", "DAILY"));
							
							if(simulationItem.getCutLossAmount_asDouble() == 0){
								double gainLoss = 0;

								if(simulationItem.getTypeOfTransaction().equals(TransactionType.LONG_TFEX)){
									gainLoss = (simulationItem.getEndIndex_asDouble() - simulationItem.getStartIndex()) * SimulationConstant.TFEX_PRICE_PER_POINT;
								}else if(simulationItem.getTypeOfTransaction().equals(TransactionType.SHORT_TEFX)){
									gainLoss = (simulationItem.getStartIndex() - simulationItem.getEndIndex_asDouble()) * SimulationConstant.TFEX_PRICE_PER_POINT;
								}else if(simulationItem.getTypeOfTransaction().equals(TransactionType.BUY_SET50)){
									gainLoss = (simulationItem.getEndIndex_asDouble() - simulationItem.getStartIndex())/simulationItem.getStartIndex() * runningBalance;
								}

								runningBalance = runningBalance + gainLoss;
								simulationItem.setBalance(runningBalance);
							}
							output.add(simulationItem);
							isInvested = false;

						}
						
						//Check if we should quite the market
						if(runningBalance < allowedMinimumRunningBalance){
							if(isInvested){
								output.add(simulationItem);
							}
							break;
						}



					}
				}
				
			}
			
			if(printDetailedSimulation){
				System.out.println(
						"StartDate;" +
						"StartIndex;" +
						"Type;" +
						"EndDate;" +
						"EndIndex;" +
						"LotToBuy;" +
						"MaxGain;" +
						"MaxLoss;" +
						"CutLossDate;" +
						"CutLossAmt;" +
						"Balance");
				for(SimulationData simulateItem:output){
					System.out.println(
							simulateItem.getStartDate() + ";" + 
							simulateItem.getStartIndex() + ";"
							+ simulateItem.getTypeOfTransaction() + ";"
							+ simulateItem.getEndDate() + ";" 
							+ simulateItem.getEndIndex() + ";" 
							+ simulateItem.getNumberOfTFEXContract() + ";"
							+ simulateItem.getMaximumPointGainForPeriod() + ";"
							+ simulateItem.getMaximumPointLossForPeriod() + ";"
							+ simulateItem.getCutLossDate() + ";" 
							+ SimulationCommonUtils.formatAmount(simulateItem.getCutLossAmount()) + ";" 
							+ SimulationCommonUtils.formatAmount(simulateItem.getBalance()));
				}
				
				System.out.println("================ Done ================");
			}
			
		} catch (SQLException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
		
		return output;
		
	}

	public static Connection getConnection() throws SQLException{

		Connection conn = (Connection) GlobalCache.getCache(serialVersionUID);

		if (conn == null) {
			conn = DriverManager.getConnection("jdbc:oracle:thin:@172.16.115.131:1521/finance", "finance", "finance");
//			conn = DriverManager.getConnection("jdbc:oracle:thin:@192.168.1.248:1521/finance", "finance", "finance");
//			conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "finance", "finance");
			conn.setAutoCommit(false);
			GlobalCache.addCache(serialVersionUID, conn);
		}
		
		return conn;
		
	}
	
}
